﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports System.Globalization
Imports System.Net
Imports System.Net.Browser
Imports System.Runtime.Serialization
Imports System.Text
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Credentials
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Serializers

<DataContract(Namespace:="", Name:="Queue")> _
Public Class CloudQueue
    Implements ICloudQueue
    Private Const MaximumClearRetries As Integer = 10

    <DataMember()>
    Public Property Name() As String Implements ICloudQueue.Name

    <DataMember(Name:="Url")>
    Public Property Uri() As Uri Implements ICloudQueue.Uri

    Public Property StorageCredentials() As IStorageCredentials

    Private ReadOnly Property BaseUri() As Uri
        Get
            Dim uriString = Me.Uri.ToString()
            Dim baseUriString = uriString.Substring(0, uriString.LastIndexOf(Me.Name, StringComparison.InvariantCulture))
            Return New Uri(baseUriString)
        End Get
    End Property

    Public Sub AddMessage(ByVal message As CloudQueueMessage, ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.AddMessage
        If message Is Nothing Then
            Throw New ArgumentNullException("message")
        End If

        Dim serviceOperation = String.Format(CultureInfo.InvariantCulture, "{0}/messages", Me.Uri)
        Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "POST"

        Dim xmlData = String.Format(CultureInfo.InvariantCulture, "<QueueMessage><MessageText>{0}</MessageText></QueueMessage>", Convert.ToBase64String(message.AsBytes))

        Me.StorageCredentials.SignRequest(request, xmlData.Length)

        request.BeginGetRequestStream(
            Sub(ar)
                Dim postStream = request.EndGetRequestStream(ar)
                Dim byteArray = Encoding.UTF8.GetBytes(xmlData)
                postStream.Write(byteArray, 0, xmlData.Length)
                postStream.Close()
                request.BeginGetResponse(
                    Sub(asyncResult)
                        Try
                            If request.HaveResponse Then
                                Dim response = CType(request.EndGetResponse(asyncResult), HttpWebResponse)
                                If response.StatusCode = HttpStatusCode.Created Then
                                    callback(New CloudOperationResponse(Of Boolean)(True, Nothing))
                                Else
                                    Dim exceptionMessage = If(response.StatusCode = HttpStatusCode.BadRequest, String.Format(CultureInfo.InvariantCulture, "Trying to add queue message too large to the queue '{0}'", Me.Name), String.Format(CultureInfo.InvariantCulture, "An error occurred while adding a message to the queue '{0}'", Me.Name))
                                    callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
                                End If
                            End If
                        Catch exception As Exception
                            callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                        End Try
                    End Sub, Nothing)
            End Sub, request)
    End Sub

    Public Sub Clear(ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.Clear
        Dim serviceOperation = String.Format(CultureInfo.InvariantCulture, "{0}/messages", Me.Uri)

        Me.InnerClear(serviceOperation, callback, 0)
    End Sub

    Public Sub Create(ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.Create
        Dim request = CType(WebRequestCreator.ClientHttp.Create(Me.Uri), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "PUT"

        Me.StorageCredentials.SignRequest(request, 0)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        If response.StatusCode = HttpStatusCode.Created Then
                            callback(New CloudOperationResponse(Of Boolean)(True, Nothing))
                        Else
                            Dim exceptionMessage = If(response.StatusCode = HttpStatusCode.NoContent OrElse response.StatusCode = HttpStatusCode.Conflict, String.Format(CultureInfo.InvariantCulture, "The queue named '{0}' already exists", Me.Name), String.Format(CultureInfo.InvariantCulture, "An error occurred while creating the queue named '{0}'", Me.Name))
                            callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
                        End If
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub,
            Nothing)
    End Sub

    Public Sub CreateIfNotExist(ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.CreateIfNotExist
        Dim request = CType(WebRequestCreator.ClientHttp.Create(Me.Uri), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "PUT"

        Me.StorageCredentials.SignRequest(request, 0)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        Dim success = response.StatusCode = HttpStatusCode.Created
                        callback(New CloudOperationResponse(Of Boolean)(success, Nothing))
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub,
            Nothing)
    End Sub

    Public Sub Delete(ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.Delete
        Dim request = CType(WebRequestCreator.ClientHttp.Create(Me.Uri), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "DELETE"

        Me.StorageCredentials.SignRequest(request, 0)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        If response.StatusCode = HttpStatusCode.NoContent Then
                            callback(New CloudOperationResponse(Of Boolean)(True, Nothing))
                        Else
                            Dim exceptionMessage = String.Format(CultureInfo.InvariantCulture, "An error occurred while deleting the queue named '{0}'", Me.Name)
                            callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
                        End If
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub, Nothing)
    End Sub

    Public Sub GetMessage(ByVal callback As Action(Of CloudOperationResponse(Of CloudQueueMessage))) Implements ICloudQueue.GetMessage
        Me.InnerGetMessages(1, False, Nothing, Sub(c) callback(New CloudOperationResponse(Of CloudQueueMessage)(If(c.Response IsNot Nothing, c.Response.SingleOrDefault(), Nothing), c.Exception)))
    End Sub

    Public Sub GetMessage(ByVal visibilityTimeout As TimeSpan, ByVal callback As Action(Of CloudOperationResponse(Of CloudQueueMessage))) Implements ICloudQueue.GetMessage
        Me.InnerGetMessages(1, False, visibilityTimeout, Sub(c) callback(New CloudOperationResponse(Of CloudQueueMessage)(If(c.Response IsNot Nothing, c.Response.SingleOrDefault(), Nothing), c.Exception)))
    End Sub

    Public Sub GetMessages(ByVal messageCount As Integer, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage)))) Implements ICloudQueue.GetMessages
        Me.InnerGetMessages(messageCount, False, Nothing, callback)
    End Sub

    Public Sub GetMessages(ByVal messageCount As Integer, ByVal visibilityTimeout As TimeSpan, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage)))) Implements ICloudQueue.GetMessages
        Me.InnerGetMessages(messageCount, False, visibilityTimeout, callback)
    End Sub

    Public Sub PeekMessage(ByVal callback As Action(Of CloudOperationResponse(Of CloudQueueMessage))) Implements ICloudQueue.PeekMessage
        Me.InnerGetMessages(1, True, Nothing, Sub(c) callback(New CloudOperationResponse(Of CloudQueueMessage)(If(c.Response IsNot Nothing, c.Response.SingleOrDefault(), Nothing), c.Exception)))
    End Sub

    Public Sub PeekMessages(ByVal messageCount As Integer, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage)))) Implements ICloudQueue.PeekMessages
        Me.InnerGetMessages(messageCount, True, Nothing, callback)
    End Sub

    Public Sub DeleteMessage(ByVal message As CloudQueueMessage, ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.DeleteMessage
        If message Is Nothing Then
            Throw New ArgumentNullException("message")
        End If

        Dim serviceOperation = String.Format(CultureInfo.InvariantCulture, "{0}/messages/{1}?popreceipt={2}", Me.Uri, message.Id, System.Net.HttpUtility.UrlEncode(message.PopReceipt))

        Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "DELETE"

        Me.StorageCredentials.SignRequest(request, 0)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        If response.StatusCode = HttpStatusCode.NoContent Then
                            callback(New CloudOperationResponse(Of Boolean)(True, Nothing))
                        Else
                            Dim exceptionMessage = String.Format(CultureInfo.InvariantCulture, "An error occurred while deleting the queue named '{0}'", Me.Name)
                            callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
                        End If
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub,
            Nothing)
    End Sub

    Public Sub Exists(ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudQueue.Exists
        Dim serviceOperation = String.Format(CultureInfo.InvariantCulture, "{0}?comp=list&prefix={1}", Me.BaseUri, Me.Name)

        ' Adding an incremental seed to avoid a cached response.
        serviceOperation = String.Concat(serviceOperation, "&incrementalSeed=", Date.UtcNow.Ticks)

        Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "GET"

        Me.StorageCredentials.SignRequest(request, -1)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        Dim serializer = New QueueDataContractSerializer(Me.StorageCredentials)
                        Dim queues = serializer.ReadObject(response.GetResponseStream())
                        Dim result = queues.SingleOrDefault(Function(q) q.Name.Equals(Me.Name))
                        callback(New CloudOperationResponse(Of Boolean)(result IsNot Nothing, Nothing))
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub,
            Nothing)
    End Sub

    Private Sub InnerClear(ByVal serviceOperation As String, ByVal callback As Action(Of CloudOperationResponse(Of Boolean)), ByVal retryCounter As Integer)
        If retryCounter > MaximumClearRetries Then
            Dim exceptionMessage = String.Format(CultureInfo.InvariantCulture, "Clear operation was not completed due to an internal time out. Please retry to finish the operation")
            callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
        Else
            Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
            request.Headers("x-ms-version") = "2009-09-19"
            request.Method = "DELETE"

            Me.StorageCredentials.SignRequest(request, 0)
            request.BeginGetResponse(
                Sub(ar)
                    Try
                        If request.HaveResponse Then
                            Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                            If response.StatusCode = HttpStatusCode.NoContent Then
                                callback(New CloudOperationResponse(Of Boolean)(True, Nothing))
                            Else
                                If response.StatusCode = HttpStatusCode.InternalServerError AndAlso response.StatusDescription.Equals("OperationTimedOut", StringComparison.Ordinal) Then
                                    retryCounter += 1
                                    Me.InnerClear(serviceOperation, callback, retryCounter)
                                Else
                                    Dim exceptionMessage = String.Format(CultureInfo.InvariantCulture, "An error occurred while clearing all the messages from the queue '{0}'", Me.Name)
                                    callback(New CloudOperationResponse(Of Boolean)(False, New Exception(exceptionMessage)))
                                End If
                            End If
                        End If
                    Catch exception As Exception
                        callback(New CloudOperationResponse(Of Boolean)(False, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                    End Try
                End Sub, Nothing)
        End If
    End Sub

    Private Sub InnerGetMessages(ByVal messageCount As Integer, ByVal peek As Boolean, ByVal visibilitytimeout? As TimeSpan, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage))))
        If messageCount < 0 OrElse messageCount > 32 Then
            callback(New CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage))(Nothing, New Exception("The number of messages to be retrieved from the queue must be greater than zero and less than 32")))
        ElseIf visibilitytimeout.HasValue AndAlso (visibilitytimeout.Value.TotalHours > 2 OrElse visibilitytimeout.Value.TotalSeconds < 0) Then
            callback(New CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage))(Nothing, New Exception("The visibility timeout must be greater than zero and less than 7200 seconds (2 hours)")))
        Else
            Dim visibilityQueryString = If(visibilitytimeout.HasValue, String.Concat("visibilitytimeout=", visibilitytimeout.Value.TotalSeconds), String.Empty)
            Dim serviceOperation = String.Format(CultureInfo.InvariantCulture, "{0}/messages?numofmessages={1}{2}{3}", Me.Uri, messageCount, If(peek, "&peekonly=true", String.Empty), visibilityQueryString)

            ' Adding an incremental seed to avoid a cached response.
            serviceOperation = String.Concat(serviceOperation, "&incrementalSeed=", Date.UtcNow.Ticks)

            Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
            request.Headers("x-ms-version") = "2009-09-19"
            request.Method = "GET"

            Me.StorageCredentials.SignRequest(request, -1)
            request.BeginGetResponse(
                Sub(ar)
                    Try
                        If request.HaveResponse Then
                            Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                            Dim serializer = New QueueMessageDataContractSerializer()
                            Dim results = serializer.ReadObject(response.GetResponseStream())
                            callback(New CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage))(results, Nothing))
                        End If
                    Catch exception As Exception
                        callback(New CloudOperationResponse(Of IEnumerable(Of CloudQueueMessage))(Nothing, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                    End Try
                End Sub, Nothing)
        End If
    End Sub
End Class